<?php
abstract class system_db_adapter_pdo_abstract extends system_db_adapter_abstract {
	protected $_defaultStmtClass = 'system_db_statement_pdo';
	
	protected function _dsn() {
		// baseline of DSN parts
		$dsn = $this->_config;
		
		// don't pass the username, password, charset, persistent and driver_options in the DSN
		unset($dsn['username']);
		unset($dsn['password']);
		unset($dsn['options']);
		unset($dsn['charset']);
		unset($dsn['persistent']);
		unset($dsn['driver_options']);
		
		// use all remaining parts in the DSN
		foreach($dsn as $key => $val) {
			$dsn[$key] = $key.'='.$val;
		}
		return $this->_pdoType.':'.implode(';', $dsn);
	}
	
	protected function _connect() {
		// if we already have a PDO object, no need to re-connect.
		if($this->_connection) {
			return;
		}
		
		// get the dsn first, because some adapters alter the $_pdoType
		$dsn = $this->_dsn();
		
		// check for PDO extension
		if(!extension_loaded('pdo')) {
			throw new system_db_adapter_exception('The PDO extension is required for this adapter but the extension is not loaded');
		}
		
		// check the PDO driver is available
		if(!in_array($this->_pdoType, PDO::getAvailableDrivers())) {
			throw new system_db_adapter_exception('The '.$this->_pdoType.' driver is not currently installed');
		}
		
		// create PDO connection
		$q = $this->getProfiler()->queryStart('connect', system_db_profiler::CONNECT);
		
		// add the persistence flag if we find it in our config array
		if(isset($this->_config['persistent'])&&($this->_config['persistent']==true)) {
			$this->_config['driver_options'][PDO::ATTR_PERSISTENT] = true;
		}
		try {
			$this->_connection = new PDO($dsn, $this->_config['username'], $this->_config['password'], $this->_config['driver_options']);
			
			$this->getProfiler()->queryEnd($q);
			
			// set the PDO connection to perform case-folding on array keys, or not
			$this->_connection->setAttribute(PDO::ATTR_CASE, $this->_caseFolding);
			
			// always use exceptions.
			$this->_connection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
		
		} catch (PDOException $e) {
			throw new system_db_adapter_exception($e->getMessage(), $e->getCode(), $e);
		}
	}
	
	public function isConnected() {
		return ((bool) ($this->_connection instanceof PDO));
	}
	public function closeConnection() {
		$this->_connection = null;
	}
	
	public function prepare($sql) {
		$this->_connect();
		$stmtClass = $this->_defaultStmtClass;
		
		$stmt = new $stmtClass($this, $sql);
		$stmt->setFetchMode($this->getFetchMode());
		return $stmt;
	}
	
	public function lastInsertId($tableName = null, $primaryKey = null) {
		$this->_connect();
		return $this->_connection->lastInsertId();
	}
	public function query($sql, $bind = array()) {
		if(empty($bind)&&$sql instanceof system_db_select) {
			$bind = $sql->getBind();
		}
		
		if(is_array($bind)) {
			foreach($bind as $name => $value) {
				if(!is_int($name)&&!preg_match('/^:/', $name)) {
					$newName = ":$name";
					unset($bind[$name]);
					$bind[$newName] = $value;
				}
			}
		}
		
		try {
			return parent::query($sql, $bind);
		} catch (PDOException $e) {
			throw new system_db_statement_exception($e->getMessage(), $e->getCode(), $e);
		}
	}
	public function exec($sql) {
		if($sql instanceof system_db_select) {
			$sql = $sql->assemble();
		}
		
		try {
			$affected = $this->getConnection()->exec($sql);
			
			if($affected===false) {
				$errorInfo = $this->getConnection()->errorInfo();
				throw new system_db_adapter_exception($errorInfo[2]);
			}
			return $affected;
		} catch (PDOException $e) {
			throw new system_db_adapter_exception($e->getMessage(), $e->getCode(), $e);
		}
	}
	
	protected function _quote($value) {
		if(is_int($value)||is_float($value)) {
			return $value;
		}
		$this->_connect();
		return $this->_connection->quote($value);
	}
	
	protected function _beginTransaction() {
		$this->_connect();
		$this->_connection->beginTransaction();
	}
	
	protected function _commit() {
		$this->_connect();
		$this->_connection->commit();
	}
	
	protected function _rollBack() {
		$this->_connect();
		$this->_connection->rollBack();
	}
	
	public function setFetchMode($mode) {
		//check for PDO extension
		if(!extension_loaded('pdo')) {
			throw new system_db_adapter_exception('The PDO extension is required for this adapter but the extension is not loaded');
		}
		switch($mode) {
			case PDO::FETCH_LAZY:
			case PDO::FETCH_ASSOC:
			case PDO::FETCH_NUM:
			case PDO::FETCH_BOTH:
			case PDO::FETCH_NAMED:
			case PDO::FETCH_OBJ:
				$this->_fetchMode = $mode;
				break;
			default:
				throw new system_db_adapter_exception("Invalid fetch mode '$mode' specified");
				break;
		}
	}
	
	public function supportsParameters($type) {
		switch($type) {
			case 'positional':
			case 'named':
			default:
				return true;
		}
	}
	
	public function getServerVersion() {
		$this->_connect();
		try {
			$version = $this->_connection->getAttribute(PDO::ATTR_SERVER_VERSION);
		} catch (PDOException $e) {
			// In case of the driver doesn't support getting attributes
			return null;
		}
		$matches = null;
		if(preg_match('/((?:[0-9]{1,2}\.){1,3}[0-9]{1,2})/', $version, $matches)) {
			return $matches[1];
		} else {
			return null;
		}
	}
}